#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<string.h>
#include<assert.h>
#include<ctype.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<errno.h>
//宏函数
#define trimSpace(start) do{\
        while(isspace(*start)) start++;\
    }while(0)
//'\'是续行符。
//再内核中经常使用do {  } while(0) ,来包含一个语句块。
//isspace () 是判断是不是空格的意思，是空格返回真
#define NUM 1024
//标记
#define NONE 0 //没有重定向
#define INPUT 1 //输入重定向
#define OUTPUT 2  //输出重定向
#define APPEND 3  // 追加重定向


char linecommand[NUM];
char* argv_[60];
int lastcode =0;
int lastsig = 0;



int redirTYpe = NONE;//重定向方式
char * redirFile = NULL;//重定向的文件名称
//重定向命名行处理
void commandCheck(char* commands)
{

    assert(commands);
    char* start = commands;
    char* end = commands+strlen(commands);
    while(start < end)
    {
        if(*start  == '>')
        {
            *(start) = '\0';
            start++;
            if(*start == '>')
            {
                start++;
                redirTYpe = APPEND;
            }
            else{
                redirTYpe = OUTPUT;
            }

            trimSpace(start);
            redirFile = start;
            break;
        }
        else if(*start == '<')
        {
            *start = '\0';
            start++;
            //去空格
            trimSpace(start);//宏函数
            redirFile = start;
            redirTYpe = INPUT;
            break;
        }
        else{
            start++;
        }
    }
}



int main()
{
    while(1)
    {
//对全局变量进行初始化
        redirFile = NULL;
        redirTYpe =NONE;


//打印提示符
        printf("用户名@主机名 当前路劲#");
        fflush(stdout);
//获取用户输入
        char* str = fgets(linecommand, NUM-1, stdin);
        assert(str != NULL);
        (void)str;
        //清楚最后一个\n的字符，
        //因为我们输入完成后会敲入一个回车键，回车也会被获取到linecommand中
        linecommand[strlen(linecommand)-1] = '\0';//消除最后的回车键
        //printf("%s\n",linecommand);//获取成功
       
//处理输出重定向 >  
//追加重定向  >>  
//输入重定向  <
        //以前我们读取的命令行一般是 ls -a -l -i 
        //假如有了重定向符号 就变成了  ls -a -l -i  >  test.txt
        //这就要我们加以处理了，怎么把输出结果 重定向到tets.txt 里面 （运用dup2函数）
        commandCheck(linecommand);


//分割linecommand，
        argv_[0] = strtok(linecommand," "); 
        int i = 1;
        //处理ls的颜色和缩写
        if(strcmp(argv_[0],"ll") == 0){argv_[0] = (char*)"ls";argv_[i++] = (char*)"-l";}
        if(strcmp(argv_[0],"ls") == 0){argv_[i++] = (char*)"--color=auto";}
        while((argv_[i++] = strtok(NULL, " "))!=NULL);
        
        //处理cd 和 echo这样的内建命令
        if(argv_[0] != NULL && strcmp(argv_[0], "cd")== 0){
            if(argv_[1] != NULL)chdir(argv_[1]);
            continue;
        }
        if(argv_[0] != NULL && argv_[1] != NULL && strcmp(argv_[0], "echo")==0 && getenv(((char*)argv_[1])+1) == NULL){
            if(strcmp(argv_[1], "$?")==0)
                printf("code:%d sig:%d\n", lastcode,lastsig);
            else
                printf("%s\n",argv_[1]);
            continue;
        }


//创建子进程，进行程序替换
        pid_t id = fork();
        assert(id != -1);
        if(id == 0 )//重定向的工作一定是子进程完成的
            //但是如何重定向，是父进程要给子进程提供信息的。
        {
            //开始重定向
            //重定向不会影响父进程。
            //进程具有独立性
            switch(redirTYpe)
            {
                case NONE://没有重定向
                    break;
                case INPUT://输入重定向
                    {
                        int fd = open(redirFile, O_RDONLY);
                        if(fd <0 )
                        {
                            perror("open");
                            exit(errno);
                        }
                        fd = dup2(fd,0);
                    }
                    break;
                case OUTPUT://输出重定向
                    {
                        int fd = open(redirFile, O_WRONLY | O_CREAT |O_TRUNC , 0666 );
                        if(fd <0)
                        {
                            perror("open");
                            exit(errno);
                        }
                        fd = dup2(fd, 1);
                         break;
                    }
                case APPEND://追加重定向
                    {
                        int fd = open(redirFile, O_WRONLY | O_CREAT | O_APPEND, 0666);
                        if(fd<0)
                        {
                            perror("open");
                            exit(errno);
                        }
                        fd = dup2(fd, 1);
                        break;
                    }

                default:
                    printf("error & bug !!1\n");

            }
            execvp(argv_[0], argv_);
            exit(1);
        }

        int status = 0;
        pid_t ret = waitpid(id, &status, 0);//阻塞等待
        //进程的程序替换的时候，不会影响曾经进程打开的文件和重定向的文件
        //进程替换的是代码和数据，进程的文件描述符表和pcb（tast_struct)是内核数据结构。是OS管理的
        //进程替换的时候不会影响他们。
        assert(ret > 0);
        (void)ret ;
        lastcode = ((status>>8)&0xff);
        lastsig = (status&0x7f);

    }
}
